package sbc.jms;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.jms.JMSException;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import org.xvsm.api.core.exceptions.IllegalValueTypeSpecifiedException;
import org.xvsm.api.core.exceptions.InvalidParameterValueException;
import org.xvsm.client.ClientCapi;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.xvsm.api.core.Capi;
import org.xvsm.api.core.CapiFactory;
import org.xvsm.api.core.ContainerRef;
import org.xvsm.api.core.CoordinationTypes;
import org.xvsm.api.core.Entry;
import org.xvsm.api.core.Tuple;
import org.xvsm.api.core.ValueTypes;
import org.xvsm.api.core.notification.NotificationID;
import org.xvsm.api.core.notification.NotificationListener;
import org.xvsm.api.core.notification.NotificationMode;
import org.xvsm.api.core.notification.NotificationTarget;
import org.xvsm.ms.exceptions.DoubleCrefException;
import org.xvsm.ms.exceptions.FatalException;
import org.xvsm.ms.exceptions.SelectorNotCompatibleException;
import org.xvsm.ms.exceptions.UnknownContainerException;
import org.xvsm.ms.exceptions.WriteSelectorNeededException;
import sbc.fabrik.model.Feature;
import sbc.fabrik.model.Platine;
import sbc.fabrik.model.Sensor;
import sbc.util.Util;

/**
 *
 * @author mustermark
 */
public class Benchmark implements Runnable {
    
    private class Timing {
   public Timing(){
       }
   public Timing(long start){
       sendTime = start;
       }
    private long sendTime = 0;
    private long recvTime = 0;

        public long getSendTime() {
            return sendTime;
        }

        public void setSendTime(long sendT) {
            sendTime = sendT;
        }

        public long getRecvTime() {
            return recvTime;
        }

        public void setRecvTime(long recvT) {
            recvTime = recvT;
        }

    }
    
    private int counter = 1;
    private int returnCounter = 0;
    private static Capi capi;
    private static ContainerRef cref;
    Context jndiContext = null;
    QueueConnectionFactory  queueConnectionFactory = null;
    QueueConnection queueConnection = null;
    Queue queue = null;
    QueueSession session = null;
    QueueSender publisher = null;
    
    Map<String, Timing> timeTable = new HashMap<String, Timing>();;
    
    /**
     * Creates a new instance of MozartToCorsoBridge
     */
    public Benchmark(Capi c) throws DoubleCrefException, FatalException {
        capi = c;
        cref = capi.createContainer(CoordinationTypes.FIFO);
        
    }
    
    public Benchmark() throws DoubleCrefException, FatalException {
        
    }
    
    
    public void disestablish() {
    }
    public void establish() {
        try {
            Properties properties = new Properties();
            properties.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
            properties.put(Context.URL_PKG_PREFIXES, "org.jnp.interfaces");
            properties.put(Context.PROVIDER_URL, "localhost");
            
            
            
            jndiContext = new InitialContext(properties);
        /*
         * Look up connection factory and topic.  If either does
         * not exist, exit.
         */
            queueConnectionFactory = (QueueConnectionFactory) jndiContext.lookup("ConnectionFactory");
            queue = (Queue) jndiContext.lookup("queue/sbc");
            
            queueConnection = queueConnectionFactory.createQueueConnection();
            session = (QueueSession) queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
            publisher = session.createSender(queue);
            
            
        } catch (NamingException e) {
            System.out.println("JNDI lookup failed: " + e.toString());
            e.printStackTrace();
            System.exit(1);
        } catch (JMSException e) {
            System.out.println("JMS: " + e.toString());
            e.printStackTrace();
            System.exit(1);
        }
    }
    
    public void route(List<Entry> entries) throws BridgeException{
        
            try {
            for(Entry en : entries) {
                if(en.getType() == ValueTypes.TUPLE) {
                    route((Tuple)en);
                    
                }
                else {
                System.out.println("not TUPLE!!!");
                }
                
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private void route(Tuple t) throws BridgeException, JMSException, Exception {
        List<Entry> elements = t.getValue();
        String type = ((String)elements.get(0).getValue()).toUpperCase();
        String id = ((String)elements.get(1).getValue());
        
        for (String str: timeTable.keySet()) {
            System.out.println(str);
        }
        System.out.println("id " + id);
        
        Timing timing = timeTable.get(id);
        if (timing != null) { 
        timing.setRecvTime(System.currentTimeMillis());
        timeTable.put(id, timing);
        } else
        {
            System.out.println("Unknown Board!");
            
        }
        
        System.out.print("BCK: " );
        
            
            for (Entry e: elements) {
                System.out.print(e.getValue() + " ");
            }
            
            System.out.print("\t" + (timeTable.get(id).getRecvTime() - timeTable.get(id).getSendTime()) + "ms");
      }

   
 
    private void writeSensor() {
                try {
                    Sensor sensor = new Sensor(String.valueOf(System.currentTimeMillis()));
                    Entry classEntry = Entry.Factory.newInstance(ValueTypes.STRING_UTF8, "SENSOR");
                    Entry idEntry = Entry.Factory.newInstance(ValueTypes.STRING_UTF8, sensor.getSid());
                    Tuple tuple = new Tuple();
                    tuple.addEntry(classEntry);
                    tuple.addEntry(idEntry);
                    
                    //int idx = Math.round(Integer.  Math.round(Math.random())));
                    //tuple.addEntry(Entry.Factory.newInstance(ValueTypes.STRING_UTF8, Feature.values()[idx]));
                    tuple.addEntry(Entry.Factory.newInstance(ValueTypes.STRING_UTF8, Feature.ACCELERATION.name()));
                    tuple.addEntry(Entry.Factory.newInstance(ValueTypes.STRING_UTF8, Feature.HUMIDITY.name()));
                    tuple.addEntry(Entry.Factory.newInstance(ValueTypes.STRING_UTF8, Feature.RADIATION.name()));
                    //System.out.println(Math.round(Math.random() % 6f));
                    List<Entry> entries = new LinkedList<Entry>();
                    entries.add(tuple);
                   
                    capi.write(cref, entries);
                    System.out.println("Wrote:" + sensor.toString());
                } catch (InvalidParameterValueException ex) {
                    ex.printStackTrace();
                } catch (UnknownContainerException ex) {
                    ex.printStackTrace();
                } catch (FatalException ex) {
                    ex.printStackTrace();
                } catch (WriteSelectorNeededException ex) {
                    ex.printStackTrace();
                } catch (SelectorNotCompatibleException ex) {
                    ex.printStackTrace();
                }
                    
             
        }
                private void writePlatine() {
        try {
            Platine p = new Platine(String.valueOf(System.currentTimeMillis()));
            
            Entry a0 = Entry.Factory.newInstance(ValueTypes.STRING_UTF8, "BOARD");
            Entry a1 = Entry.Factory.newInstance(ValueTypes.STRING_UTF8, p.getPid());
            Tuple t1 = new Tuple();
            t1.addEntry(a0);
            t1.addEntry(a1);
            List<Entry> entries = new LinkedList<Entry>();
            entries.add(t1);
            
            capi.write(cref, entries);
            
            timeTable.put(p.getPid(), new Timing(System.currentTimeMillis()));
            
            
            System.out.println("Wrote:" + p.toString());
        } catch (IllegalValueTypeSpecifiedException ex) {
            ex.printStackTrace();
        } catch (InvalidParameterValueException ex) {
            ex.printStackTrace();
        } catch (WriteSelectorNeededException ex) {
            ex.printStackTrace();
        } catch (UnknownContainerException ex) {
            ex.printStackTrace();
        } catch (FatalException ex) {
            ex.printStackTrace();
        } catch (SelectorNotCompatibleException ex) {
            ex.printStackTrace();
        }
                     
                }

    
    public void feed() {
                    /* starting feeding xvsm */
            
            for (int c = 1; c <= getCounter(); c++) {
            try {
                writeSensor();
                Thread.sleep(1000);
                writeSensor();
                Thread.sleep(1000);
                writePlatine();
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                
            }                   
                    
                }
                
                                          
            
            System.out.println("******************************");
            
            
            //while (true) {
                try {
                    // millisecs
                    Thread.sleep(1000);
                    //System.out.println(bridge.returnCounter + "<>" + counter);
                  /*  if (bridge.returnCounter == getCounter()) {
                        System.out.println("RESULTS: " + bridge.returnCounter + " of " + getCounter() + "boards assembled");
                       double db = ((bridge.lastRecvTime - bridge.firstSendTime)/counter);
                        System.out.print("         " + (bridge.lastRecvTime - bridge.firstSendTime) );
                        System.out.print((1/db));
                        System.out.print(" ");
                        System.out.print((db)); 
                    };*/
                } catch (Exception ex) {
                } finally {
                    
                    capi.shutdown(true);
                    
                }
            //} while
            
    }
    
    
    public void run() {
        
        try {
            System.out.println("run");
            //final ContainerRef cref = Util.getOrCreateNamedContainer(capi, "DefaultContainer", CoordinationTypes.FIFO);
            capi.createNotification(cref, -1, NotificationTarget.WRITE.code(), NotificationMode.INFINITE, true, new NotificationListener() {
                
                
                
                public void sendNotification(NotificationID id) {}
                public void sendNotification(NotificationID id, java.util.List<Entry> entries) {
                    
                    //System.out.println("sending " + id + " with " + entries);
                    //route(entries);
                    try {
                        String type = "";
                        Entry en = entries.get(0);
                        //if(en.getType() == ValueTypes.TUPLE) {
                            Tuple tuple = (Tuple) entries.get(0);
                            List<Entry> elements = tuple.getValue();
                            type = ((String)elements.get(0).getValue()).toUpperCase();
                            System.out.println("--" + type);
                        //}
                        if (type.startsWith("UNIT"))
                            route(capi.take(cref));                            
                        
                    } catch(Exception ex) {
                        System.out.println("Couldn't route " + entries);
                        ex.printStackTrace();
                    }
                }
                
                
            });
        } catch(Exception ex) { ex.printStackTrace(); }
        
    }
    
    // }
    
    public static void main(String[] args) {
        try {
            Logger.getLogger("org.xvsm.client").setLevel(Level.FATAL);
            
            if (args.length < 1) {
                System.out.println("using embeded xvsm core");
                capi = CapiFactory.createCapi();
                //capi = new ClientCapi("http://localhost:8084/xvsm-0.8.3");
            } else {
                // Get an instance of the capi and initialize it.
                // Capi capi = CapiFactory.createCapi();
                capi = new ClientCapi(args[0]);
            }
            
            capi.init(false);
            //cref = capi.createContainer(CoordinationTypes.FIFO);
            cref = Util.getOrCreateNamedContainer(capi, "DefaultContainer", CoordinationTypes.FIFO);
            
            Benchmark bridge = (Benchmark) new Benchmark();
            if (args[1] != null) bridge.setCounter(Integer.parseInt(args[1]));

            bridge.establish();
            bridge.run();
            bridge.feed();
            
        } catch (Exception  e) {
            e.printStackTrace();
            
        }
    }
    public static void help() {
        System.out.println("\n*** FabrikShell Commands ***\n");
        System.out.println("\\s, sensor <id> <feature> [[<feature>] [<feature>]]: new sensor.");
        System.out.println("\\p, platine <id>: new platine.");
        System.out.println("\\r, read: read space.");
        System.out.println("\\q, quit: quit program.");
        System.out.println("\\h, help: this message.\n");
        System.out.println("****************************\n");
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }
    
    
}
